(function () {
  const socket = io({
    transports: ['websocket'],
  });

  const canCopy = Boolean(getSelection);

  const classCopy = 'copy';
  const classLastRead = 'last-read';

  const docRoot = document.documentElement;
  const docBody = document.body;
  const receivedElement = document.getElementById('received');
  const formElement = document.getElementById('form');
  const sayingElement = document.getElementById('saying');
  const btnSend = formElement.send;

  let lastSay = undefined;

  // scroll
  function updateScrollPosition() {
    docRoot.scrollTop = docBody.scrollTop =
      docRoot.scrollHeight - (window.innerHeight || document.docRoot.clientHeight);
  }

  function onConnect() {
    const roomName = location.pathname;
    socket.emit('join', {roomName});

    btnSend.disabled = false;
  }

  function onDisconnect() {
    btnSend.disabled = true;
  }

  // login
  function onLoginRequired() {
    const storageKey = 'userName';
    let userName = '';

    const param = new URLSearchParams(location.search);
    userName = param.get(storageKey);

    if (!userName) {
      try {
        userName = sessionStorage.getItem(storageKey) || localStorage.getItem(storageKey);
      } catch (ex) {}
    }

    if (!userName) {
      userName = prompt('Input your nick name');
    }

    if (!userName) {
      return;
    }

    try {
      sessionStorage.setItem(storageKey, userName);
      localStorage.setItem(storageKey, userName);
    } catch (ex) {}

    const roomName = location.pathname;

    socket.emit('submitlogin', {userName, roomName});
    formElement.saying.focus();
  }

  function onLoginSuccess() {
    submitLastSay();
    lastSay = undefined;
  }

  // process messages
  const reUrl = /(\b(https?|ftp|file):\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;"'`<>()\[\]{}]+)/g;
  function urlify(text) {
    const html = text.replace(reUrl, url => {
      const link = htmlEncode(url);
      return `<a href="${link}">${link}</a>`;
    });
    return html;
  }
  function htmlEncode(text) {
    return text.replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&apos;');
  }

  function appendMessages(messages, isNew) {
    const frag = document.createDocumentFragment();
    messages.forEach(([userName, content]) => {
      const elMessage = document.createElement('div');
      if (isNew) {
        elMessage.classList.add('new');
      }

      if (canCopy) {
        const btnCopy = document.createElement('button');
        btnCopy.classList.add(classCopy);
        btnCopy.textContent = 'Copy';
        elMessage.appendChild(btnCopy);
      }

      const elUserName = document.createElement('span');
      elUserName.appendChild(document.createTextNode(userName));
      elMessage.appendChild(elUserName);

      const elContent = document.createElement('span');
      elContent.innerHTML = urlify(content);
      elMessage.appendChild(elContent);

      frag.appendChild(elMessage);
    });
    receivedElement.appendChild(frag);
    updateScrollPosition();
  }

  function onSaidsReceived(messages) {
    messages = messages.slice(receivedElement.childElementCount);
    if (messages.length) {
      appendMessages(messages, false);
      restoreLastReadBar();
    }
  }

  function onSaysReceived(messages) {
    if (messages.length) {
      appendMessages(messages, true);
    }
  }

  function onSubmitSay(e) {
    e.preventDefault();
    let say = sayingElement.value;
    if (say) {
      sayingElement.value = '';
      lastSay = say.trim();
      submitLastSay();
    }
    sayingElement.focus();
  }

  function submitLastSay() {
    if (lastSay) {
      if (!socket.connected) socket.connect();
      socket.emit('submitsay', lastSay);
    }
  }

  function onSayingKeyPress(e) {
    if (!e.shiftKey && (e.keyCode === 13 || e.keyCode === 10)) {
      e.preventDefault();
      btnSend.click(); // trigger form submit
    }
  }

  function clearLastReadBar() {
    receivedElement.querySelectorAll('.' + classLastRead).forEach(el => {
      el.classList.remove(classLastRead);
    });
  }

  const receivedStorageKey = 'received';
  function appendLastReadBar() {
    const received = receivedElement.childElementCount;
    if (received) {
      receivedElement.lastElementChild.classList.add(classLastRead);
    }
    sessionStorage.setItem(receivedStorageKey, received);
  }
  function restoreLastReadBar() {
    const strReceived = sessionStorage.getItem(receivedStorageKey);
    if (strReceived === null) return;
    sessionStorage.removeItem(receivedStorageKey);
    if (!isFinite(strReceived)) return;

    const lastIndex = parseInt(strReceived, 10) - 1;
    const el = receivedElement.children[lastIndex];
    if (!el) return;

    el.classList.add(classLastRead);
  }

  function onCopy(e) {
    const button = e.target;
    const elRow = button.parentNode;
    const elMessage = elRow.children[elRow.childElementCount - 1];
    if (elMessage) {
      // select
      const selection = window.getSelection();
      if (selection.rangeCount) {
        selection.removeAllRanges();
      }
      const range = document.createRange();
      range.selectNode(elMessage);
      selection.addRange(range);

      // copy
      try {
        try {
          if (navigator.clipboard) {
            navigator.clipboard.writeText(elMessage.textContent.trim());
          } else {
            throw new Error('Clipboard API not supported');
          }
        } catch (err) {
          if (!document.execCommand('copy')) {
            throw err;
          }
        }
      } catch (err) {
        console.error(err);
        return;
      }

      // bubble
      const icon = document.createElement('em');
      icon.addEventListener('animationend', () => icon.remove());
      button.appendChild(icon);
    }
  }

  function onOnline() {
    if (!socket.connected) socket.connect();
  }

  function onPageHide() {
    clearLastReadBar();
    appendLastReadBar();
  }

  function onPageShow() {
    if (!socket.connected) socket.connect();
  }

  function onVisibilityChange() {
    const {hidden} = document;
    if (hidden) {
      onPageHide();
    } else {
      onPageShow();
    }
  }

  formElement.addEventListener('submit', onSubmitSay);
  sayingElement.addEventListener('keypress', onSayingKeyPress);

  receivedElement.addEventListener('click', function (e) {
    switch (e.target.className) {
      case classCopy:
        onCopy.call(this, e);
        break;
    }
  });

  window.addEventListener('online', onOnline);
  window.addEventListener('pagehide', onPageHide);
  window.addEventListener('pageshow', onPageShow);
  document.addEventListener('visibilitychange', onVisibilityChange);

  socket.on('connect', onConnect);
  socket.on('disconnect', onDisconnect);
  socket.on('loginrequired', onLoginRequired);
  socket.on('loginsuccess', onLoginSuccess);
  socket.on('saids', onSaidsReceived);
  socket.on('says', onSaysReceived);
})();
